import { SCREEN_HEIGHT, SCREEN_WIDTH } from "consts";
import {
  compileGameGlobalsHeader,
  compileScrollBounds,
  parallaxStep,
  toASMCollisionGroup,
} from "lib/compiler/generateGBVMData";
import { CollisionExtraFlag } from "shared/lib/resources/types";

describe("compileGameGlobalsHeader", () => {
  test("should include variables, constants and state references in global header", () => {
    const output = compileGameGlobalsHeader(
      {
        var1: {
          id: "var1",
          name: "Variable 1",
          symbol: "VAR_1",
          isLocal: false,
          entityType: "scene",
          entityId: "",
          sceneId: "",
        },
        var2: {
          id: "var2",
          name: "Variable 2",
          symbol: "VAR_2",
          isLocal: false,
          entityType: "scene",
          entityId: "",
          sceneId: "",
        },
      },
      [
        {
          symbol: "CONST_0",
          id: "const0",
          name: "Constant 0",
          value: 0,
        },
        {
          symbol: "CONST_1",
          id: "const1",
          name: "Constant 1",
          value: 64,
        },
      ],
      { PLATFORM_CONST_1: 5 },
      ["STATE_DEFAULT", "STATE_EXPLODE", "STATE_OPEN"],
    );
    expect(output).toInclude("VAR_1 0");
    expect(output).toInclude("VAR_2 1");
    expect(output).toInclude("MAX_GLOBAL_VARS 2");
    expect(output).toInclude("CONST_0 0");
    expect(output).toInclude("CONST_1 64");
    expect(output).toInclude("PLATFORM_CONST_1 5");
    expect(output).toInclude("STATE_DEFAULT 0");
    expect(output).toInclude("STATE_EXPLODE 1");
    expect(output).toInclude("STATE_OPEN 2");
  });
});

describe("parallaxStep", () => {
  test("should convert parallax inputs to gbvm macro string", () => {
    expect(parallaxStep(0, 5, 5)).toBe("PARALLAX_STEP(0, 5, 5)");
  });

  test("should wrap parallax speeds to within signed 8-bit range", () => {
    expect(parallaxStep(4, 8, 128)).toBe("PARALLAX_STEP(4, 8, -128)");
  });
});

describe("toASMCollisionGroup", () => {
  test("should generate correct collision groups", () => {
    expect(toASMCollisionGroup("player")).toBe("COLLISION_GROUP_PLAYER");
    expect(toASMCollisionGroup("1")).toBe("COLLISION_GROUP_1");
    expect(toASMCollisionGroup("2")).toBe("COLLISION_GROUP_2");
    expect(toASMCollisionGroup("3")).toBe("COLLISION_GROUP_3");
  });

  test("should generate none collision groups if invalid collision group", () => {
    expect(toASMCollisionGroup("INVALID_COLLISION_GROUP")).toBe(
      "COLLISION_GROUP_NONE",
    );
  });

  test("should concatenate extras flags correctly", () => {
    expect(toASMCollisionGroup("1", ["1"])).toBe(
      "COLLISION_GROUP_1 | COLLISION_GROUP_FLAG_1",
    );
    expect(toASMCollisionGroup("1", ["2", "3"])).toBe(
      "COLLISION_GROUP_1 | COLLISION_GROUP_FLAG_2 | COLLISION_GROUP_FLAG_3",
    );
  });

  test("should ignore invalid extras flags correctly and generate a valid ASM", () => {
    const invalidFlag1 = "" as CollisionExtraFlag;
    const invalidFlag2 = "invalid_flag" as CollisionExtraFlag;

    expect(toASMCollisionGroup("1", [invalidFlag1])).toBe("COLLISION_GROUP_1");
    expect(toASMCollisionGroup("1", [invalidFlag2])).toBe("COLLISION_GROUP_1");
    expect(toASMCollisionGroup("1", [invalidFlag1, invalidFlag2])).toBe(
      "COLLISION_GROUP_1",
    );
    expect(toASMCollisionGroup("1", ["1", invalidFlag2])).toBe(
      "COLLISION_GROUP_1 | COLLISION_GROUP_FLAG_1",
    );
  });
});

describe("compileScrollBounds", () => {
  test("should convert scroll bounds to pixel coordinates", () => {
    const scrollBounds = {
      x: 2,
      y: 3,
      width: 25,
      height: 20,
    };

    const result = compileScrollBounds(scrollBounds, 20, 30);

    expect(result).toEqual({
      left: 16,
      top: 24,
      right: 56,
      bottom: 40,
    });
  });

  test("should handle minimal scroll bounds", () => {
    const scrollBounds = {
      x: 0,
      y: 0,
      width: 20,
      height: 18,
    };

    const result = compileScrollBounds(scrollBounds, 20, 30);

    expect(result).toEqual({
      left: 0,
      top: 0,
      right: 0,
      bottom: 0,
    });
  });

  test("should handle large scroll bounds", () => {
    const scrollBounds = {
      x: 5,
      y: 10,
      width: 250,
      height: 250,
    };

    const result = compileScrollBounds(scrollBounds, 20, 30);

    expect(result).toEqual({
      left: 40,
      top: 80,
      right: 1880,
      bottom: 1936,
    });
  });

  test("should make sure width/height are greater than screen size", () => {
    const scrollBounds = {
      x: 2,
      y: 2,
      width: 15,
      height: 12,
    };

    const result = compileScrollBounds(scrollBounds, 20, 30);

    expect(result).toEqual({
      left: 16,
      top: 16,
      right: 16,
      bottom: 16,
    });
  });

  test("should return the given screen size when scrollBounds is undefined", () => {
    const result = compileScrollBounds(undefined, 20, 30);
    expect(result).toEqual({
      left: 0,
      top: 0,
      right: (20 - SCREEN_WIDTH) * 8,
      bottom: (30 - SCREEN_HEIGHT) * 8,
    });
  });
});
